AT Real-Time Clock, Part 1 -- 1400 Words Jim Mischel 443-8777 The AT Real-Time Clock Part 1: The BIOS Interface Inside every IBM AT or compatible computer is a Motorola MC146818 Real-Time Clock (RTC) chip (or functional equivalent). This particular part provides a real-time clock, 14 bytes of clock, calendar and control registers along with 50 bytes of general- purpose RAM all backed up by a battery that allows the information to be retained when the system is shut down. In addition, the chip is capable of generating a hardware interrupt at a program-specified frequency or time. In the IBM AT, The primary function of the Real-Time Clock (RTC) is to retain time, date and system setup information when the system is shut down. At power-on, the BIOS POST (power on self- test) code reads system setup information from the battery backed-up RAM and sets the system clock from the battery backed- up clock. Under DOS, the chip is then completely ignored unless specifically activated by an application program. So? What's it good for? The AT BIOS provides additional interrupts and functions that make use of the RTC's features. Specifically, BIOS interrupt 15H, functions 83H (Set/Cancel Wait Interval) and 86H (Wait), and interrupt 1AH (Time-of-Day Service) make use of the RTC chip to provide real-time control capabilities not found in the PC BIOS. A summary of these functions is shown in Table 1. INT 1AH, the BIOS Time-of-Day service, is provided in the PC BIOS as well as the AT BIOS. In the PC, only functions 0 and 1 are provided. These functions read and set the System Timer Time Counter; the number of "clock ticks" since midnight. The clock ticks approximately 18.2 times per second, or once every 54.9 milliseconds. These functions provide a simpler and more accurate method of timing operations than the MS-DOS clock function (INT 21H, function 2DH). The AT BIOS adds six new functions to INT 1AH as shown in Table 1. The first four--functions that read and set the RTC time and date--are of little real use to the applications program. The only program that should attempt to set the RTC time or date is the SETUP program provided with your system software and reading the clock shouldn't be necessary unless you've had interrupts disabled for an extended period and want to reset the DOS clock. Functions 6 and 7, the alarm functions, provide you with a way to interrupt a process at a specific time. Just as with a normal alarm clock, you set the alarm and then go about your business. When the time comes, the alarm rudely interrupts, causing you to do something else. INT 15H provides two wait functions that perform subtly different tasks. Function 83H sets a wait interval and allows processing to continue. When the wait interval expires a flag is set at a user-specified memory location. Think of function 83H as an hourglass egg timer: all it tells you is whether or not the time period is over, not how much time has elapsed or how much time is left--and you have to look at it in order to find out. You can stop an egg timer at any moment by turning it on its side. Function 83H has a "turned on side" mode--subfunction 1. Function 86H, on the other hand, suspends processing until the specified time period has elapsed. Think of function 86H as a traffic signal that you control, but with a catch. This red light can't be run. (A few of those here in Phoenix and nobody would get anywhere!) Unfortunately, the designers of the AT BIOS decided that functions 83H and 86H should share data areas. As a result, these functions cannot be used concurrently. The rationale behind this design decision escapes me, as I can think of several situations in which concurrent use of these functions would be beneficial, if not required. However, since these functions do share data areas, remember: you can't boil eggs while waiting for the light to turn green. OK. So how does it work? The BIOS support of reading and setting the real-time clock is relatively straightforward. The details of reading and writing the CMOS RAM will be left to the next installment as they are not pertinent to this discussion. For INT 1AH functions 2 through 5, the BIOS simply accesses the CMOS RAM and returns the status of the operation. There is really not much to it. The Wait and Alarm functions, on the other hand, require a little background processing. I mentioned earlier that the MC146818 is capable of generating a hardware interrupt. It is through this interrupt and the Real- Time Clock ISR (INT 70H) that the Wait and Alarm functions are processed. The RTC ISR in turn uses the BIOS data areas shown in Table 2. A block diagram of the RTC interface is shown in figure 1 and the BIOS RTC update logic is shown in figure 2. What do you mean, "It doesn't work"? Sadly, some AT BIOS clones do not correctly implement all of the functions--the Phoenix BIOS in my Kaypro 286i being a case in point. After 2 days of tearing my hair out trying to figure out why my functions wouldn't work, I finally decided that the problem was in the BIOS (see sidebar "Bugs in the BIOS?"). Fortunately I was able to identify and correct the problems. Using the BIOS functions RTCBIOS.C (Listing 1) illustrates the use of the RTC functions and provides the timer functions Wait(), SetWait() and CancelWait(); and the Real-Time Clock functions GetRTCtime(), SetRTCtime(), GetRTCdate(), SetRTCdate(), SetAlarm() and ClearAlarm(). If compiled with the TESTING #define removed, the resulting object file can be linked with other programs or included in a library. This program works correctly under all Turbo C++ memory models. The only problem I had was with TC++ 1.0 failing to emit an external declaration in TINY model. This problem was fixed as shown in the AlarmInterrupt() function. The first part of the test program simply sets a wait interval, prompts for a key, and then reports whether the key was pressed before or after the wait interval expires. It then waits for one second before prompting for the next key. Pressing ESC will continue to the next part of the program. The only function here that warrants discussion is SetWait(). The calling program must supply a wait interval and the address of a byte that will have its high bit set when the wait interval expires. This variable must be declared using the volatile keyword and should be cleared by the program before calling SetWait(). Although a little more involved than the first part, the second part of the test program is still quite simple. It reads and displays the current date and time and then sets an alarm to sound 30 seconds after the current time. If you wish to exit the program before the alarm sounds, simply press any key. The AT BIOS configures the RTC to store time and date information in BCD format. In order to more easily manipulate these values in C, the functions that access the RTC time and date fields accept and return binary, rather than BCD values. The functions themselves handle the conversions. SetAlarm() requires that you supply it with the address of the interrupt service routine that will process the alarm interrupt. When the alarm time is reached, the BIOS invokes INT 4AH to process the alarm. As with most interrupt routines, this function should be small and fast and it cannot use DOS calls. You must save the current alarm interrupt vector (INT 4AH) using getvect() before calling SetAlarm() and you must restore it using setvect() before exiting the program. Failure to save and restore this vector can lead to some very strange behavior--a system crash at a seemingly random time. With the functions presented here, AT programmers can create more accurate time delays and exert better real-time control than is possible using PC BIOS functions. But even the AT functions are lacking in several areas. Not being able to use Wait() and SetWait() concurrently is the biggest drawback. The ability to query the alarm time or set more than one alarm would also be useful. These capabilities and more are all possible, but require direct manipulation of the real-time clock hardware--the subject of the second part of this article.